/*
 * Copyright (c) 2012 Robert Burrell Donkin robertburrelldonkin.name
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
 * and associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 
 * is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or 
 * substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * 
 */
package walrus.base;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Reader;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.util.ConcurrentModificationException;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.lang.CharUtils;

public class ArticleBuffer {

	private static final String NAME_SUFFIX = ".txt";

	private static final String NAME_PREFIX = "article-";

	private static final Charset UTF8 = Charset.forName("UTF-8");
	
	private static AtomicInteger instanceNumber = new AtomicInteger(0);
	
	public static final int K = 1024;

	private static final int OUT_BUFFER_CAPACITY = 8*K;
	public static final int M = 1024 * K;
	public static final int DEFAULT_BUFFER = 8 * M;

	private StringBuilder title = new StringBuilder();
	private final CharBuffer buffer;
	private final ByteBuffer outBuffer;
	private final CharsetEncoder encoder;
	private final int number;
	
	private volatile boolean saving = false;

	public ArticleBuffer() {
		this(DEFAULT_BUFFER);
	}

	public ArticleBuffer(final int bufferSize) {
		super();
		buffer = CharBuffer.allocate(bufferSize);
		this.number = instanceNumber.getAndIncrement();
		outBuffer = ByteBuffer.allocate(OUT_BUFFER_CAPACITY);
		encoder = UTF8.newEncoder();
	}

	public void reset() {
		buffer.clear();
		title = new StringBuilder();
	}
	
	private String moniker() {
		return "BUFFER[" + number + "] ";
	}

	public String getTitleHash() {
		return DigestUtils.md5Hex(getTitle());
	}
	
	public String getTitle() {
		return title.toString().trim();
	}

	public ArticleBuffer title(char[] ch, int start, int length) {
		if (saving) throw new ConcurrentModificationException();
		title.append(ch, start, length);
		return this;
	}

	public ArticleBuffer text(char[] ch, int start, int length)
			throws BufferOverflowException {
		if (saving) throw new ConcurrentModificationException();
		buffer.put(ch, start, length);
		return this;
	}

	@Override
	public String toString() {
		return moniker() + " (title=" + getTitle() + ")";
	}

	public void logTo(PrintStream out) {
		StringBuilder builder = new StringBuilder("number=");
		builder.append(number);
		builder.append(" title=");
		builder.append(getTitle());
		builder.append(" md5=");
		builder.append(getTitleHash());
		out.println(builder);
	}

	public ArticleBuffer feed(CharProcessor  processor) throws IOException {
		saving = true;
		final int originalPosition = buffer.position();
		try {
			buffer.flip();
			while (buffer.hasRemaining()) {
				processor.process(buffer.get());
			}
		} finally {
			buffer.clear();
			buffer.position(originalPosition);
			saving = false;
		}
		return this;
	}
	
	public ArticleBuffer saveTo(File file) throws IOException {
		final FileOutputStream out = new FileOutputStream(file);
		saveTo(out.getChannel()).close();
		out.close();
		return this;
	}
	
	public WritableByteChannel saveTo(WritableByteChannel out) throws IOException {
		saving = true;
		final int originalPosition = buffer.position();
		try {
			buffer.flip();
			encoder.reset();
	
			outBuffer.clear();
			boolean more = true;
			while (more) {
				final CoderResult result = encoder.encode(buffer, outBuffer, true);
				outBuffer.flip();
				out.write(outBuffer);
				outBuffer.clear();
				more = CoderResult.OVERFLOW.equals(result);
			}
			return out;
		} finally {
			buffer.clear();
			buffer.position(originalPosition);
			saving = false;
		}
	}

	
	public void saveTo(ZipArchiveOutputStream out) throws IOException {
		saving = true;
		final int originalPosition = buffer.position();
		try {
			buffer.flip();
			encoder.reset();
			final String name = NAME_PREFIX + getTitleHash() + NAME_SUFFIX;
			final ZipArchiveEntry entry = new ZipArchiveEntry(name);
			entry.setComment(getTitle());
			out.putArchiveEntry(entry);
	
			outBuffer.clear();
			boolean more = true;
			while (more) {
				final CoderResult result = encoder.encode(buffer, outBuffer, true);
				out.write(outBuffer.array(), 0, outBuffer.position());
				outBuffer.clear();
				more = CoderResult.OVERFLOW.equals(result);
			}
			
			out.closeArchiveEntry();
		} finally {
			buffer.clear();
			buffer.position(originalPosition);
			saving = false;
		}
	}
	
	public CharBuffer toBuffer() {
		return (CharBuffer) buffer.asReadOnlyBuffer().flip();
	}

	public void setTitle(String title) {
		this.title  = new StringBuilder(title);
	}

	public void textLine(String line) {
		buffer.append(line);
		buffer.append(CharUtils.CR);
		buffer.append(CharUtils.LF);
	}

	public void read(Reader in) throws IOException {
		int read = 0;
		while (read >=0) {
			read = in.read(buffer.array(), buffer.position(), buffer.capacity());
			buffer.position(read);
		}
	}
}
